﻿#light

module PubSim.Bartender

open Microsoft.FSharp.Control
open System.Collections.Generic
open System.Threading
open PubSim.Glasses
open PubSim.Cups
open PubSim.CappuccinoMix
open PubSim.Chocolate
open PubSim.Coffee
open PubSim.Milk
open PubSim.Tap

// time in milliseconds
let timeToPourBeer =   4000 
let timeToMakeCoffee = 1500
let timeToMakeCappuccino = 2000
let timeToMakeHotChocolate = 2000

type Bartender(outputQueue : Queue<string>, customerQueue : Queue<PubSim.Customer.Customer>, glasses : Glasses, cups : Cups, chocolate : Chocolate, coffee : Coffee, milk : Milk, cappuccinoMix : CappuccinoMix, tap : Tap) =
    member g.log(s : string) =
        outputQueue.Enqueue(sprintf "Bartender %d: %s" System.Threading.Thread.CurrentThread.ManagedThreadId s)
        ignore()
    member g.Start() =
        while (true) do
            if customerQueue.Count > 0 then
                let customer = customerQueue.Dequeue()
                let drink = customer.FetchDrink()
                g.log(sprintf "Got drink from customer: %s" drink)
        
                if drink = "beer" then
                    // Fetch a glass from the cupboard or wait until a glass becomes available
                    while (glasses.RemoveGlass() = false) do
                        g.log("Waiting for a glass")
                        Thread.Sleep(100)
                    g.log("Got a glass")
            
                    // Use the tap to fill the glass or wait until the tap is available
                    while (tap.UseTap() = false) do
                        g.log("Waiting for the tap")
                        Thread.Sleep(100)
                    g.log("Got the tap")
            
                    // Make the drink
                    g.log("Start filling beer")
                    Thread.Sleep(timeToPourBeer)
                    g.log("Done filling beer")
            
                    // Release the tap
                    tap.ReleaseTap()
            
                    // Give the drink to the user
                    customer.ReceiveDrink()
                    ignore()
                else if drink = "coffee" then
                    // Fetch a cup from the cupboard or wait until a cup becomes available
                    while (cups.RemoveCup() = false) do
                        g.log("Waiting for a cup")
                        Thread.Sleep(100)
                    g.log("Got a cup")
           
                    // Fetch the coffee or wait until it becomes available
                    while (coffee.FetchCoffeeFromCupboard() = false) do
                        g.log("Waiting for coffee")
                        Thread.Sleep(100)
                    g.log("Got the coffee")

                    // Make the drink
                    g.log("Start making coffee")
                    Thread.Sleep(timeToMakeCoffee)
                    g.log("Done making coffee")

                    // Put coffee back
                    coffee.AddCoffeeToCupboard()

                    // Give the drink to the user
                    customer.ReceiveDrink()
                    ignore()
                else if drink = "cappuccino" then
                    // Fetch a cup from the cupboard or wait until a cup becomes available
                    while (cups.RemoveCup() = false) do
                        g.log("Waiting for a cup")
                        Thread.Sleep(100)
                    g.log("Got a cup")

                    // Must do this in same order to prevent deadlock

                    // Fetch the cappuccino mix or wait until it becomes available
                    while (cappuccinoMix.FetchCappuccinoMixFromCupboard() = false) do
                        g.log("Waiting for cappuccino mix")
                        Thread.Sleep(100)
                    g.log("Got the cappuccino mix")

                    // Fetch the milk or wait until it becomes available
                    while (milk.FetchMilkFromCupboard() = false) do
                        g.log("Waiting for milk")
                        Thread.Sleep(100)
                    g.log("Got the milk")

                    // Make the drink
                    g.log("Start making cappuccino")
                    Thread.Sleep(timeToMakeCappuccino)
                    g.log("Done making cappuccino")

                    // Put cappuccino mix in cupboard
                    cappuccinoMix.AddCappuccinoMixToCupboard()

                    // Put milk in cupboard
                    milk.AddMilkToCupboard()

                    // Give the drink to the user
                    customer.ReceiveDrink()
                    ignore()
                else if drink = "hot chocolate" then
                    // Fetch a cup from the cupboard or wait until a cup becomes available
                    while (cups.RemoveCup() = false) do
                        g.log("Waiting for a cup")
                        Thread.Sleep(100)
                    g.log("Got a cup")

                    // Must do this in same order to prevent deadlock

                    // Fetch the chocoloate or wait until it becomes available
                    while (chocolate.FetchChocolateFromCupboard() = false) do
                        g.log("Waiting for chocolate")
                        Thread.Sleep(100)
                    g.log("Got the chocolate")

                    // Fetch the milk or wait until it becomes available
                    while (milk.FetchMilkFromCupboard() = false) do
                         g.log("Waiting for milk")
                         Thread.Sleep(100)
                    g.log("Got the milk")

                    // Make the drink
                    g.log("Start making hot chocolate")
                    Thread.Sleep(timeToMakeHotChocolate)
                    g.log("Done making hot chocolate")

                    // Put chocolate mix in cupboard
                    chocolate.AddChocolateToCupboard()

                    // Put milk in cupboard
                    milk.AddMilkToCupboard()

                    // Give the drink to the user
                    customer.ReceiveDrink()
                    ignore()
            Thread.Sleep(100)
